// TODO rewrite most of this

use std::sync::Arc;

use ecksport_rpc::errors::RpcError;
use ecksport_rpc::prelude::*;
use futures::TryFutureExt;
use tracing::*;

use aspect_codec::ecksport::AsAspCodec;
use aspect_ident::device::DirPackage;
use aspect_proto::errors::AspProtoError;
use aspect_proto::user::UserCreds;

use crate::storage::{HomeserverStorage, StorageError};

pub struct UserServer {
    storage: Arc<HomeserverStorage>,
}

impl UserServer {
    pub fn new(storage: Arc<HomeserverStorage>) -> Arc<Self> {
        Arc::new(Self { storage })
    }

    async fn create_user(&self, creds: &UserCreds) -> Result<(), AspProtoError> {
        // TODO should we consolidate this with the handler method?

        info!(username = %creds.username, "creating user");

        self.storage
            .create_user(&creds.username, &creds.password)
            .map_err(AspProtoError::server)
            .await?;

        Ok(())
    }

    async fn register_device(
        &self,
        username: &str,
        password: &str,
        pkg: &DirPackage,
    ) -> Result<(), AspProtoError> {
        let ident = *pkg.last_update().ident();
        info!(%username, %ident, "registering device to user");

        if self
            .storage
            .check_user_password(&username, &password)
            .map_err(AspProtoError::server)
            .await
            .is_err()
        {
            return Err(AspProtoError::InvalidPassword(()));
        }

        match self
            .storage
            .register_device_with_user(&username, pkg.clone())
            .await
        {
            Ok(_) => Ok(()),
            Err(StorageError::DeviceAlreadyRegistered(_)) => {
                warn!(%ident, "tried to register device but was already claimed");
                Err(AspProtoError::IdentityAlreadyClaimed(ident))
            }
            Err(e) => Err(AspProtoError::server(e)),
        }
    }
}

impl aspect_proto::core::UserServ for UserServer {
    fn user_register(
        &self,
        _pdata: PeerData,
        username: String,
        password: String,
        extra_cred: Vec<u8>,
    ) -> impl std::future::Future<Output = RpcResult<()>> + Send {
        async move {
            // TODO sanity check username
            let user = username.clone();

            if !extra_cred.is_empty() {
                warn!(%username, "user registration had extra data");
            }

            let creds = UserCreds { username, password };
            self.create_user(&creds).map_err(RpcError::from).await?;

            info!(%user, "created user");

            Ok(())
        }
    }

    fn user_add_device(
        &self,
        _pdata: PeerData,
        creds: AsAspCodec<UserCreds>,
        package: AsAspCodec<DirPackage>,
    ) -> impl std::future::Future<Output = RpcResult<()>> + Send {
        async move {
            // FIXME switch to using something more like Ecksport's authentication
            let username = creds.inner().username.clone();
            let password = creds.inner().password.clone();

            self.register_device(&username, &password, package.inner())
                .map_err(RpcError::from)
                .await?;

            let ident = package.inner().last_update().ident();
            info!(%username, %ident, "registered device to user");

            Ok(())
        }
    }
}
